package ogb;

import java.awt.Color;
import java.util.Random;
import robocode.AdvancedRobot;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.MoveCompleteCondition;
import robocode.RobotDeathEvent;
import robocode.Rules;
import robocode.ScannedRobotEvent;

/**
 * Designed specifically to counter the sample.Walls robot.
 * Moves around as Walls does, but attempts to do so in opposite direction
 * of its target, forcing a head-to-head confrontation at some point.
 * With other enemies, runs similar to Walls, though with slightly more random
 * movement and a bit more complexity.
 * Whether or not this is actually effective is debatable.
 * 
 * @author Branden Ogata
 *
 */

public class DeaconBlues extends AdvancedRobot {
  private String target;
  private int firstCorner;
  private boolean movedToFirstCorner;
  private boolean isWalls;
  private boolean isClockwise;
  private boolean isCounterClockwise;
  private boolean isFollowing;
  private int previousCorner;
  private int advisedCorner;
  private boolean immobile;
  private boolean doubleback;
  
  /**
   * Initializes many fields and handles the general operations of DeaconBlues.
   * 
   */

  public void run() {
    setColors(Color.WHITE, Color.DARK_GRAY, Color.BLUE);
    
    Random generator = new Random();
    int randomCorner = generator.nextInt(3);
    target = null;

    // Throughout this code:
    //  Corner 0 == bottom left,
    //  Corner 1 == top left,
    //  Corner 2 == top right, and
    //  Corner 3 == bottom right
    int currentCorner = 0;
    advisedCorner = 0;
    boolean suggestion = false;
    doubleback = false;
    
    while (true) {
      while (target == null) {
      // Rotate to the left
        for (int i = 0; i < 4; i++) {
          turnRadarLeft(45);
        }
        // Rotate to the right
        for (int j = 0; j < 8; j++) {
          turnRadarRight(45);
        }
        // Recenter the radar
        for (int k = 0; k < 4; k++) {
          turnRadarLeft(45);
        }
      }

      // If the target is not a Walls-type,
      // aim toward the center from a counter-clockwise perspective
      if (!isWalls) {
        isClockwise = true;
        setTurnGunLeft(90);
      }
      
      // The above loop exits only if the target is not null.
      // Therefore, a target must have been found, 
      // and if a target has been found then AntiWalls will have
      // moved to the first corner already
      while (target != null) {
        // Check for doubling back; reversing directions makes it too easy
        // for RamFire and similar robots to catch up
        if (previousCorner == currentCorner) {
          doubleback = true;
        }
        
        // If the enemy is a Walls-type
        if (isWalls) {
          setTurnGunRight(getHeading() - getGunHeading());
          
          // If the scanner provided a suggested corner to intercept the enemy at,
          // then move there at earliest possible opportunity
          if (suggestion) {
            previousCorner = currentCorner;
            currentCorner = advisedCorner;
            
            // Reset values
            advisedCorner = generator.nextInt(3);
            suggestion = false;
          }
          
          // Move along the walls
          if ((isClockwise) || 
              ((isCounterClockwise) && (isFollowing))) {
/*            out.println("Recognized as Walls-Clockwise"); */
            previousCorner = currentCorner;
            moveToCorner(currentCorner--);
            
            if (currentCorner < 0) {
              currentCorner = 3;  
            }
          }
          else if ((isCounterClockwise) ||
                    ((isClockwise) && (isFollowing))) {
/*            out.println("Recognized as Walls-Counterclockwise"); */
            previousCorner = currentCorner;
            moveToCorner(currentCorner++);
            
            if (currentCorner > 3) {
              currentCorner = 0;
            }
          }
        }
        
        // Otherwise, implement "standard" Walls tactics
        else {
          // Move to a random corner
          moveToCorner(randomCorner);
          
          // Selects the random corner to move to
          // Though using randomCorner is a bit inefficient,
          // it prevents this robot from going to the same corner again
          while (randomCorner == currentCorner) {
            randomCorner = generator.nextInt(3);
            
            // If doubleback is true, then cannot go back to same corner
            if ((doubleback) && (randomCorner == previousCorner)) {
              randomCorner = currentCorner;
            }
          }          

          previousCorner = currentCorner;
          currentCorner = randomCorner;
        }
      }
    }    
  }
  
  /**
   * Upon hitting an enemy, continue forward so as to ram into the enemy.
   * In addition, turn the cannon to aid in firing at the enemy.
   * If own energy is less than that of the enemy though,
   * break off and evade, since ramming will not work in own favor.
   * If contact is not from the front, then face enemy and move elsewhere.
   * 
   * @param e The event that the game sends.
   * 
   */
  
  public void onHitRobot(HitRobotEvent e) {
    double turnGunAngle = getHeading() + e.getBearing() - getGunHeading();
    
    if ((turnGunAngle % 360) > 180) {
      turnGunLeft(360 - (turnGunAngle % 360));
    }
    else {
      turnGunRight(turnGunAngle % 360);
    }
    fire(3);

    if ((e.getBearing() > -64.0) && (e.getBearing() < 64.0) && 
        (e.getEnergy() < (getEnergy() + 16.0))) {
      setAhead(128);
    }
    else if ((e.getBearing() < 244.0) && (e.getBearing() > 116.0)) {
      setAhead(-128);
    }
    else {
      setTurnRight(e.getBearing() - getHeading());
      setAhead(-256);
      
      // Finds a corner to move to
      moveToCorner(advisedCorner);
    }
  }

  /**
   * If a robot is destroyed and that robot is the target, reset.
   * 
   * @param e The event that the game sends.
   * 
   */
  
  public void onRobotDeath(RobotDeathEvent e) {
    if (e.getName().equals(target)) {
      target = null;
      movedToFirstCorner = false;
      isWalls = false;
      isClockwise = false;
      isCounterClockwise = false;
      isFollowing = false;
      immobile = true;
      doubleback = false;
    }
  }

  /**
   * If a bullet hits, evade and turn cannon to face where the bullet came from.
   * 
   * @param e The event that the game sends.
   * 
   */
  
  public void onHitByBullet(HitByBulletEvent e) {
    setTurnGunRight(e.getBearing() + getHeading() - getGunHeading());
        
    // If the bearing to the bullet is along one of the relative compass points,
    // then enemy is probably lined up with DeaconBlues, so evade
    if (Math.abs(e.getBearing() % 90) < 10.24) {
      // Move to center, then to another corner
      setTurnRight(calculateBearing((getBattleFieldHeight() / 2) - getX(), 
                                    (getBattleFieldWidth() / 2) - getY()) - getHeading());
      setAhead(calculateDistance((getBattleFieldHeight() / 2) - getX(), 
                                  (getBattleFieldWidth() / 2) - getY()));
      moveToCorner(advisedCorner);
    }
  }
  
  /**
   * Upon seeing an enemy, determine if it is a Walls-type robot.
   * If so, then use anti-Walls strategy.
   * Else, use Walls strategy.
   * 
   * @param e The event that the game sends.
   */
  
  public void onScannedRobot(ScannedRobotEvent e) {
    // If no target, set the target
    if (target == null) {
 /*     out.println("Found target"); */
      target = e.getName();
      immobile = true;
      isFollowing = false;
      scan();
    }
    
    // If the target is a Walls type, then skip everything else and fire
    else if (target.equals(e.getName()) && (isWalls)) {
      fire(calculateShotPower(e.getDistance(), e.getBearing(), 
                              e.getHeading(), e.getVelocity()));
      
      // Examine location of enemy
      double enemyX = getX() + calculatePointX(e.getDistance(), e.getBearing());
      double enemyY = getY() + calculatePointY(e.getDistance(), e.getBearing());
      double enemyHeading = e.getHeading();

      // Check if this robot is "following" the target
      if (e.getHeading() == getHeading()) {
        isFollowing = true;
      }

      // Updates information about the Walls enemy;
      // details given below in the last else-if case
      if (enemyX < (2 * getWidth())) {
        if (enemyHeading == 0.0) {
          isClockwise = true;
          isCounterClockwise = false;
          advisedCorner = 0;
        }
        else if (enemyHeading == 180.0) {
          isClockwise = false;
          isCounterClockwise = true;
          advisedCorner = 1;
        }
      }
      else if ((getBattleFieldWidth() - enemyX) < (2 * getWidth())) {        
        if (enemyHeading == 180.0) {
          isClockwise = true;
          isCounterClockwise = false;
          advisedCorner = 2;
        }
        else if (enemyHeading == 0.0) {
          isClockwise = false;
          isCounterClockwise = true;
          advisedCorner = 3;
        }
      }
      else if (enemyY < (2 * getWidth())) {
        if (enemyHeading == 270.0) {
          isClockwise = true;
          isCounterClockwise = false;
          advisedCorner = 2;
        }
        else if (enemyHeading == 90.0) {
          isClockwise = false;
          isCounterClockwise = true;
          advisedCorner = 1;
        }
      }
      else if ((getBattleFieldHeight() - enemyY) < (2 * getWidth())) {
        if (enemyHeading == 90.0) {
          isClockwise = true;
          isCounterClockwise = false;
          advisedCorner = 1;
        }
        else if (enemyHeading == 270.0) {
          isClockwise = false;
          isCounterClockwise = true;
          advisedCorner = 2;
        }
      }    
    }
    
    // If the current robot is the target, identify its type 
    // Though this does entail going through the entire process of categorization
    // again for non-Walls enemies, this is necessary to account for the time
    // necessary for Walls to reach a wall and its patterns to become recognizable
    else if (target.equals(e.getName())) {
      // Despite all of the below, the important thing is that there is
      // an enemy in front, so fire.
      fire(calculateShotPower(e.getDistance(), e.getBearing(), 
                              e.getHeading(), e.getVelocity()));
      
      double enemyX = getX() + calculatePointX(e.getDistance(), e.getBearing());
      double enemyY = getY() + calculatePointY(e.getDistance(), e.getBearing());
      double enemyHeading = e.getHeading();
      
      firstCorner = 0;
      
      // Checks if the enemy is moving
      if (e.getVelocity() != 0.0) {
        immobile = false;
      }
      
      // Check if this robot is "following" the target
      if (e.getHeading() == getHeading()) {
        isFollowing = true;
      }
      
      // If the target is near a wall 
      // and has a heading of 0, 90, 180, or 270,
      // then assume it is a Walls-type robot.
      // For all the below, the conditions for enemyHeading
      // account for clockwise movement (as with the original Walls)
      // and counterclockwise movement.
      if (enemyX < (2 * getWidth())) {
        if (enemyHeading == 0.0) {
          isWalls = true;
          isClockwise = true;
          isCounterClockwise = false;
          firstCorner = 3;
          moveToCorner(firstCorner);
        }
        else if (enemyHeading == 180.0) {
          isWalls = true;
          isClockwise = false;
          isCounterClockwise = true;
          firstCorner = 2;
          moveToCorner(firstCorner);
        }
        else {
          isWalls = false;
          isClockwise = false;
          isCounterClockwise = false;
        }
      }
      else if ((getBattleFieldWidth() - enemyX) < (2 * getWidth())) {        
        if (enemyHeading == 180.0) {
          isWalls = true;
          isClockwise = true;
          isCounterClockwise = false;
          firstCorner = 1;
          moveToCorner(firstCorner);
        }
        else if (enemyHeading == 0.0) {
          isWalls = true;
          isClockwise = false;
          isCounterClockwise = true;
          firstCorner = 0;
          moveToCorner(firstCorner);
        }
        else {
          isWalls = false;
          isClockwise = false;
          isCounterClockwise = false;
        }
      }
      else if (enemyY < (2 * getWidth())) {
        if (enemyHeading == 270.0) {
          isWalls = true;
          isClockwise = true;
          isCounterClockwise = false;
          firstCorner = 2;
          moveToCorner(firstCorner);
        }
        else if (enemyHeading == 90.0) {
          isWalls = true;
          isClockwise = false;
          isCounterClockwise = true;
          firstCorner = 1;
          moveToCorner(firstCorner);
        }
        else {
          isWalls = false;
          isClockwise = false;
          isCounterClockwise = false;
        }
      }
      else if ((getBattleFieldHeight() - enemyY) < (2 * getWidth())) {
        if (enemyHeading == 90.0) {
          isWalls = true;
          isClockwise = true;
          isCounterClockwise = false;
          firstCorner = 0;
          moveToCorner(firstCorner);
        }
        else if (enemyHeading == 270.0) {
          isWalls = true;
          isClockwise = false;
          isCounterClockwise = true;
          firstCorner = 3;
          moveToCorner(firstCorner);
        }
        else {
          isWalls = false;
          isClockwise = false;
          isCounterClockwise = false;
        }
      }
    }    
  }
  
  /**
   * Moves to a corner, then prepares to fire along a wall.
   * Since the direction of the enemy robot is "known" in the case 
   * of Walls-type enemies, one can guess which direction the enemy 
   * is coming from.
   * 
   * @param corner  The corner to move to.
   */

  public void moveToCorner(int corner) {
    if ((!movedToFirstCorner) || (isWalls)) {
      switch (corner) {
        case 0:        
          move((getWidth() / 2) - getX(), (getHeight() / 2) - getY());
          movedToFirstCorner = true;
          break;
        case 1:
          move((getWidth() / 2) - getX(), 
               getBattleFieldHeight() - (getHeight() / 2) - getY());
          movedToFirstCorner = true;
          break;
        case 2:
          move(getBattleFieldWidth() - (getWidth() / 2) - getX(), 
                getBattleFieldHeight() - (getHeight() / 2) - getY());
          movedToFirstCorner = true;
          break;
        case 3:
          move(getBattleFieldWidth() - (getWidth() / 2) - getX(), 
                (getHeight() / 2) - getY());
          movedToFirstCorner = true;
          break;
        default:
          break;
      }
    }
    else {
      switch (corner) {
        case 0:
          moveOrthagonal((getWidth() / 2), (getHeight() / 2));
          break;
        case 1:
          moveOrthagonal((getWidth() / 2), 
                          getBattleFieldHeight() - (getHeight() / 2));
          break;
        case 2:
          moveOrthagonal((getBattleFieldWidth() - (getWidth() / 2)), 
                          getBattleFieldHeight() - (getHeight() / 2));
          break;
        case 3:
          moveOrthagonal((getBattleFieldWidth() - (getWidth() / 2)),
                          (getHeight() / 2));
          break;
        default:
          break;
        }
      }
/*    out.println("Moved to corner " + corner); */
  }
    
  /**
   * Moves the robot to its destination using only orthagonal movements.
   * While normally inefficient, since this robot will only move along
   * the x and y axes most of the time there is no significant loss here.
   * If the enemy is not a Walls type, then this also aims the gun 
   * generally toward the center of the field, regardless of which direction 
   * this robot is moving.
   * 
   * @param destinationX  The x coordinate to move to.
   * @param destinationY  The y coordinate to move to.
   * 
   */
  
  public void moveOrthagonal(double destinationX, double destinationY) {
    // If moving right
    if (destinationX > getX()) {
      turnRight((90 - getHeading()) % 360);
    }
    // If moving left
    else if (destinationX < getX()) {
      turnLeft((90 + getHeading()) % 360);
    }

    // Only if the enemy is not a Walls type
    if (!isWalls) {
      if (getY() < (getBattleFieldHeight() / 2)) {
        turnGunToAbsoluteHeading(0);      
      }
      else if (getY() > (getBattleFieldHeight() / 2)) {
        turnGunToAbsoluteHeading(180);
      }
    }    
    setAhead(Math.abs(destinationX - getX()));    
    waitFor(new MoveCompleteCondition(this));
    
    // If moving up
    if (destinationY > getY()) {
      turnLeft(getHeading() % 360);
    }
    // If moving down
    else if (destinationY < getY()) {
      turnRight((180 - getHeading()) % 360);
    }

    // Only if the enemy is not a Walls type
    if (!isWalls) {
      if (getX() < (getBattleFieldWidth() / 2)) {
        turnGunToAbsoluteHeading(90);
      }
      else if (getX() > (getBattleFieldWidth() / 2)) {
        turnGunToAbsoluteHeading(270);
      }
    }
    
    setAhead(Math.abs(destinationY - getY()));
    waitFor(new MoveCompleteCondition(this));
  }
  
  /**
   * Turns the cannon to the specified degree.
   * 
   * @param aimDegree The degree to turn the cannon to.
   * 
   */
  
  public void turnGunToAbsoluteHeading(double aimDegree) {
    if (aimDegree > getGunHeading()) {
      setTurnGunRight(aimDegree - getGunHeading());
    }
    else {
      setTurnGunLeft(getGunHeading() - aimDegree);
    }
  }
  
  /**
   * Returns the strength at which the cannon should fire.
   * Calculations based on distance to the enemy, bearing to the enemy,
   * enemy heading, and enemy velocity.
   * Each of these is weighted an equal 0.25 of the total shot power.
   * A special case is if enemy velocity is 0 or if the bearing to the enemy
   * is 180 degrees opposite of the enemy heading, as these should both be easy shots.
   * 
   * @param distance  The distance to the enemy.
   * @param bearing The bearing to the enemy.
   * @param enemyHeading  The heading of the enemy.
   * @param enemyVelocity The velocity of the enemy.
   * 
   * @return  shotPower The power suggested for the shot, based on the parameters.
   * 
   */
  
  public double calculateShotPower(double distance, double bearing, 
                                    double enemyHeading, double enemyVelocity) {
    double shotPower = 0;
    
    // Part I: Distance
    // The further away the enemy is, the weaker the shot should be
    double distanceFactor = 0.25 * distance / 
                            ((getBattleFieldHeight() + getBattleFieldWidth()) / 2);
    
    // Part II: Bearing
    // Shots further away from orthogonal bearings should be weaker.
    // Though not tested, it certainly seems that shots not on orthogonal
    // bearings tend to miss more often.  Additionally, this robot will be 
    // moving mostly on orthogonal headings, so orthogonal bearings should line
    // up with where this robot is moving more often
    double bearingFactor = 0.25 - 
                          (0.25 * Math.abs(Math.sin(Math.toRadians(2 * bearing))));
    
    // Part III: Enemy Heading
    // Enemy headings further from the bearing to the enemy should be weaker.
    // In the simplest case, if the enemy heading is equal to or 180 degrees from
    // the bearing to the enemy, then the shot will be along the same axis of movement
    // and the shot should hit
    double headingFactor = 0.25 - (0.25 * 
                            Math.abs(Math.sin(Math.toRadians(enemyHeading - bearing))));
    
    // Part IV: Enemy Velocity
    // Faster enemies are harder to hit and should receive weaker shots
    double velocityFactor = 0.25 - (0.25 * enemyVelocity / Rules.MAX_VELOCITY);
    
    shotPower = Rules.MAX_BULLET_POWER * (distanceFactor + bearingFactor + 
                                          headingFactor + velocityFactor);
        
    // Case where enemy velocity is 0 and has never been observed otherwise
    if ((enemyVelocity == 0.0) && (immobile)) {
      shotPower = Rules.MAX_BULLET_POWER;
    }
    
    // Case where the enemy heading is 180 degrees away from the bearing to the enemy
    // This would be handled if headingFactor == 0.25, but FindBugs does not like it
    // when people test for floating-point equality.
    if ((enemyHeading + 180.0 - bearing <= 0.16) || (enemyHeading - 180.0 - bearing <= 0.16)) {
      shotPower = Rules.MAX_BULLET_POWER;
    }
    
    return (shotPower);
  }
  
  /**
   * Moves the robot given the differences in coordinates.
   * 
   * @param diffX The horizontal distance between the robot and the destination.
   * @param diffY The vertical distance between the robot and the destination.
   */
  
  public void move(double diffX, double diffY) {
    // Calculate the bearing.
    double bearing = 0;
    if (diffY < 0) {
      bearing = calculateBearing(diffX, diffY) - 180;
    }
    else if (diffY != 0) {
      bearing = calculateBearing(diffX, diffY);
    }
    
    // Turn to face in the correct direction.
    // If a turn is more than 180 degrees,
    // it is faster to turn the other direction.
    if ((bearing - getHeading()) > 180) {
/*      out.println("Turning " + (360 - bearing - getHeading())); */
      turnRight(360 - bearing - getHeading());
    }
    else if ((bearing - getHeading()) < -180) {
/*      out.println("Turning " + (bearing - getHeading() + 360)); */
      turnRight((bearing - getHeading()) + 360);
    }
    else {
/*      out.println("Turning " + (bearing - getHeading())); */
      turnRight(bearing - getHeading());      
    }
    
    // Move until reaching the destination.
    ahead(calculateDistance(diffX, diffY));
  }  
  
  /**
   * Calculates the bearing that the robot should take.
   * 
   * @param diffX The horizontal distance between the location of the robot
   *              and the destination.
   * @param diffY The vertical distance between the location of the robot
   *              and the destination.
   * @return  The bearing in degrees that the robot should take to reach the 
   *          intended destination.
   */
  
  public double calculateBearing(double diffX, double diffY) {
    return (Math.toDegrees(Math.atan(diffX / diffY)));
  }

  /**
   * Calculate the distance that the robot should take.
   * 
   * @param diffX The horizontal distance between the location of the robot
   *              and the destination.
   * @param diffY The vertical distance between the location of the robot
   *              and the destination.
   * @return  The distance between the robot and the destination.
   */
  
  public double calculateDistance(double diffX, double diffY) {
    return (Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)));
  }

  /**
   * Calculates the relative x coordinate of a point.
   * 
   * @param distance  The distance to the point.
   * @param bearing   The bearing to the point.
   * 
   * @return  The relative x coordinate.
   */
  
  public double calculatePointX(double distance, double bearing) {
    double pointX = 0.0;
    
    // If point is in first or third quadrant relative to robot,
    // then sin will find the x coordinate.
    if (((bearing > 0) && (bearing < 90)) || (bearing < -90)) {
      pointX = distance * Math.sin(Math.toRadians(bearing % 90));
    }
    // Else cos applies.
    else {
      pointX = distance * Math.cos(Math.toRadians(bearing % 90));
    }
    
    return (pointX);
  }

  /**
   * Calculates the relative y coordinate of a point.
   * 
   * @param distance  The distance to the point.
   * @param bearing   The bearing to the point.
   * 
   * @return  The relative y coordinate.
   */
  
  public double calculatePointY(double distance, double bearing) {
    double pointY = 0.0;
    
    // If point is in first or third quadrant relative to robot,
    // then cos will find the y coordinate.
    if (((bearing > 0) && (bearing < 90)) || (bearing < -90)) {
      pointY = distance * Math.cos(Math.toRadians(bearing % 90));
    }
    // Else cos applies.
    else {
      pointY = distance * Math.sin(Math.toRadians(bearing % 90));
    }
    
    return (pointY);
  }  
}
